﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KontoraME
{
    public class Node
    {
        private int id;
        private Point point;
        private Size size;
        private List<GameObject> listObjects;
        private List<Body> listBodies;
        private Node LeftTop;
        private Node RightTop;
        private Node LeftBot;
        private Node RightBot;

        private int maxWidth;
        

        public Node(int _id, Point _p, Size _s, int _maxWidth)
        {
            this.id = _id;
            this.point = _p;
            this.size = _s;
            this.maxWidth = _maxWidth;
            listObjects = new List<GameObject>();
            listBodies = new List<Body>();
            LeftTop = null;
            RightTop = null;
            LeftBot = null;
            RightBot = null;
        }

        //Kiểm tra Node trống hay không
        public bool IsEmpty
        {
            get
            {
                if (LeftBot == null && listObjects.Count == 0)
                    return true;
                return false;
            }
        }
        //Id của Node
        public int Id { get { return id; } }

        public Rectangle rec 
        {
            get 
            {
                Rectangle r = new Rectangle(point.X, maxWidth - point.Y, size.Width, size.Height);
                return r;
            }
            set 
            {
                point.X = value.X;
                point.Y = value.Y;
                size.Width = value.Width;
                size.Height = value.Height;
            }
        }
        
        //Số node con của node
        public int Count
        {
            get
            {
                int count = 0;

                if (LeftBot != null)
                {
                    count += LeftTop.Count;
                    count += RightTop.Count;
                    count += LeftBot.Count;
                    count += RightBot.Count;
                }
                count += this.listObjects.Count;
                return count;
            }
        }
        //Các object nằm trong node
        public List<GameObject> ListObjects { get { return listObjects; } }
        //Các body nằm trong node
        public List<Body> ListBodies { get { return listBodies; } }
        //Các object nằm trong cây con của node
        public List<GameObject> SubTreeObjects
        {
            get
            {
                List<GameObject> results = new List<GameObject>();

                if (LeftBot != null)
                {
                    results.AddRange(LeftTop.SubTreeObjects);
                    results.AddRange(RightTop.SubTreeObjects);
                    results.AddRange(LeftBot.SubTreeObjects);
                    results.AddRange(RightBot.SubTreeObjects);
                }

                results.AddRange(this.listObjects);
                return results;
            }
        }

        //Các body nằm trong cây con của node
        public List<Body> SubTreeBodies
        {
            get
            {
                List<Body> results = new List<Body>();

                if (LeftBot != null)
                {
                    results.AddRange(LeftTop.SubTreeBodies);
                    results.AddRange(RightTop.SubTreeBodies);
                    results.AddRange(LeftBot.SubTreeBodies);
                    results.AddRange(RightBot.SubTreeBodies);
                }

                results.AddRange(this.listBodies);
                return results;
            }
        }
        //Thêm đối tượng game vào tree
        public void Insert(GameObject _obj)
        {
            Rectangle r = new Rectangle(
                _obj.Point.X - _obj.Size.Width / 2,
                maxWidth - _obj.Point.Y - _obj.Size.Height / 2,
                _obj.Size.Width,
                _obj.Size.Height
                );
            this.listObjects.Add(_obj);
            if (LeftBot == null && LeftTop == null && RightBot == null && RightTop == null)
                CreateSubNodes();
            if (LeftTop != null && LeftTop.rec.IntersectsWith(r))
            {
                LeftTop.Insert(_obj);
                if (this.listObjects.Contains(_obj))
                    this.listObjects.Remove(_obj);
            }
            if (RightTop != null && RightTop.rec.IntersectsWith(r))
            {
                RightTop.Insert(_obj);
                if (this.listObjects.Contains(_obj))
                    this.listObjects.Remove(_obj);
            }
            if (LeftBot != null && LeftBot.rec.IntersectsWith(r))
            {
                LeftBot.Insert(_obj);
                if (this.listObjects.Contains(_obj))
                    this.listObjects.Remove(_obj);
            }
            if (RightBot != null && RightBot.rec.IntersectsWith(r))
            {
                RightBot.Insert(_obj);
                if (this.listObjects.Contains(_obj))
                    this.listObjects.Remove(_obj);
            }   
        }

        //Thêm body vào tree
        public void InsertBody(Body _body, int axisY)
        {
            Rectangle r = new Rectangle(
                _body.Point.X,
                maxWidth - (axisY - _body.Point.Y),
                _body.Size.Width,
                _body.Size.Height
                );

            this.listBodies.Add(_body);
            if (LeftBot == null && LeftTop == null && RightBot == null && RightTop == null)
            {
                CreateCSubNodes();
            }
            if (LeftTop != null && LeftTop.rec.IntersectsWith(r))
            {
                LeftTop.InsertBody(_body, axisY);
                if (this.listBodies.Contains(_body))
                    this.listBodies.Remove(_body);
            }
            if (RightTop != null && RightTop.rec.IntersectsWith(r))
            {
                RightTop.InsertBody(_body, axisY);
                if (this.listBodies.Contains(_body))
                    this.listBodies.Remove(_body);
            }
            if (LeftBot != null && LeftBot.rec.IntersectsWith(r))
            {
                LeftBot.InsertBody(_body, axisY);
                if (this.listBodies.Contains(_body))
                    this.listBodies.Remove(_body);
            }
            if (RightBot != null && RightBot.rec.IntersectsWith(r))
            {
                RightBot.InsertBody(_body, axisY);
                if (this.listBodies.Contains(_body))
                    this.listBodies.Remove(_body);
            }
        }
        //Tạo các node con
        private void CreateSubNodes()
        {
            if ((size.Width <= GlobalConstants.TREE_SIZE && size.Height < GlobalConstants.TREE_SIZE))
                return;
            
            int halfWidth = size.Width / 2;
            int halfHeight = size.Height / 2;

            LeftBot = new Node(this.id * 8 + 3, new Point(point.X, point.Y - halfHeight), new Size(halfWidth, halfHeight), this.maxWidth);
            RightBot = new Node(this.id * 8 + 4, new Point(point.X + halfWidth, point.Y - halfHeight), new Size(halfWidth, halfHeight), this.maxWidth);
            LeftTop = new Node(this.id * 8 + 1, point, new Size(halfWidth, halfHeight), this.maxWidth);
            RightTop = new Node(this.id * 8 + 2, new Point(point.X + halfWidth, point.Y), new Size(halfWidth, halfHeight), this.maxWidth);
        }
        //Tạo các node con
        private void CreateCSubNodes()
        {
            if ((size.Width <= GlobalConstants.COLLISION_SIZE && size.Height < GlobalConstants.COLLISION_SIZE))
                return;

            int halfWidth = size.Width / 2;
            int halfHeight = size.Height / 2;

            LeftBot = new Node(this.id * 8 + 3, new Point(point.X, point.Y - halfHeight), new Size(halfWidth, halfHeight), this.maxWidth);
            RightBot = new Node(this.id * 8 + 4, new Point(point.X + halfWidth, point.Y - halfHeight), new Size(halfWidth, halfHeight), this.maxWidth);
            LeftTop = new Node(this.id * 8 + 1, point, new Size(halfWidth, halfHeight), this.maxWidth);
            RightTop = new Node(this.id * 8 + 2, new Point(point.X + halfWidth, point.Y), new Size(halfWidth, halfHeight), this.maxWidth);
        }
        //Xén đối tượng vào quadtree
        public List<GameObject> Query(Rectangle queryArea)
        {
            // create a list of the items that are found
            List<GameObject> results = new List<GameObject>();

            // this quad contains items that are not entirely contained by
            // it's four sub-quads. Iterate through the items in this quad 
            // to see if they intersect.
            foreach (GameObject obj in this.listObjects)
            {
                Rectangle r = new Rectangle(obj.Point, obj.Size);
                if (queryArea.IntersectsWith(r))
                    results.Add(obj);
            }

            if (LeftTop != null)
            {

                if (LeftTop.rec.Contains(queryArea))
                    results.AddRange(LeftTop.Query(queryArea));

                if (queryArea.Contains(LeftTop.rec))
                    results.AddRange(LeftTop.SubTreeObjects);

                if (LeftTop.rec.IntersectsWith(queryArea))
                    results.AddRange(LeftTop.Query(queryArea));
            }

            if (RightTop != null)
            {
                if (RightTop.rec.Contains(queryArea))
                    results.AddRange(RightTop.Query(queryArea));

                if (queryArea.Contains(RightTop.rec))
                    results.AddRange(RightTop.SubTreeObjects);

                if (RightTop.rec.IntersectsWith(queryArea))
                    results.AddRange(RightTop.Query(queryArea));
            }

            if (LeftBot != null)
            {
                if (LeftBot.rec.Contains(queryArea))
                    results.AddRange(LeftTop.Query(queryArea));

                if (queryArea.Contains(LeftBot.rec))
                    results.AddRange(LeftBot.SubTreeObjects);

                if (LeftBot.rec.IntersectsWith(queryArea))
                    results.AddRange(LeftBot.Query(queryArea));
            }

            if (RightBot != null)
            {
                if (RightBot.rec.Contains(queryArea))
                    results.AddRange(RightBot.Query(queryArea));

                if (queryArea.Contains(RightBot.rec))
                    results.AddRange(RightBot.SubTreeObjects);

                if (RightBot.rec.IntersectsWith(queryArea))
                    results.AddRange(RightBot.Query(queryArea));
            }


            return results;
        }

        //Xén body vào quadtree
        public List<Body> QueryBody(Rectangle queryArea)
        {
            // create a list of the items that are found
            List<Body> results = new List<Body>();

            // this quad contains items that are not entirely contained by
            // it's four sub-quads. Iterate through the items in this quad 
            // to see if they intersect.
            foreach (Body body in this.listBodies)
            {
                Rectangle r = new Rectangle(body.Point, body.Size);
                if (queryArea.IntersectsWith(r))
                    results.Add(body);
            }

            if (LeftTop != null)
            {

                if (LeftTop.rec.Contains(queryArea))
                    results.AddRange(LeftTop.QueryBody(queryArea));

                if (queryArea.Contains(LeftTop.rec))
                    results.AddRange(LeftTop.SubTreeBodies);

                if (LeftTop.rec.IntersectsWith(queryArea))
                    results.AddRange(LeftTop.QueryBody(queryArea));
            }

            if (RightTop != null)
            {
                if (RightTop.rec.Contains(queryArea))
                    results.AddRange(RightTop.QueryBody(queryArea));

                if (queryArea.Contains(RightTop.rec))
                    results.AddRange(RightTop.SubTreeBodies);

                if (RightTop.rec.IntersectsWith(queryArea))
                    results.AddRange(RightTop.QueryBody(queryArea));
            }

            if (LeftBot != null)
            {
                if (LeftBot.rec.Contains(queryArea))
                    results.AddRange(LeftTop.QueryBody(queryArea));

                if (queryArea.Contains(LeftBot.rec))
                    results.AddRange(LeftBot.SubTreeBodies);

                if (LeftBot.rec.IntersectsWith(queryArea))
                    results.AddRange(LeftBot.QueryBody(queryArea));
            }

            if (RightBot != null)
            {
                if (RightBot.rec.Contains(queryArea))
                    results.AddRange(RightBot.QueryBody(queryArea));

                if (queryArea.Contains(RightBot.rec))
                    results.AddRange(RightBot.SubTreeBodies);

                if (RightBot.rec.IntersectsWith(queryArea))
                    results.AddRange(RightBot.QueryBody(queryArea));
            }


            return results;
        }

        //Lưu quad tree xuống file
        public void Save(StreamWriter sw)
        {
            int subnode = 0;
            if (LeftTop != null)
                subnode = 4;
            sw.Write(id + "\t\t" + point.X.ToString("00000") + "\t\t" + point.Y.ToString("00000") + "\t\t" + size.Width.ToString("00000") + "\t\t" + size.Height.ToString("00000") + "\t\t" + subnode + "\t\t\t" + listObjects.Count + "\t\t\t");
            if (listObjects.Count > 0)
                foreach (GameObject o in listObjects)
                    sw.Write(o.Id + "\t");
            sw.WriteLine();
            if (LeftTop != null)
            {
                LeftTop.Save(sw);
                RightTop.Save(sw);
                LeftBot.Save(sw);
                RightBot.Save(sw);
            }
        }
        //Lưu quad tree va chạm xuống file
        public void SaveBody(StreamWriter sw)
        {
            int subnode = 0;
            if (LeftTop != null)
                subnode = 4;
            sw.Write(id + "\t\t" + point.X.ToString("00000") + "\t\t" + point.Y.ToString("00000") + "\t\t" + size.Width.ToString("00000") + "\t\t" + size.Height.ToString("00000") + "\t\t" + subnode + "\t\t\t" + this.listBodies.Count + "\t\t\t");
            if (this.listBodies.Count > 0)
                foreach (Body b in listBodies)
                    sw.Write(b.BodyId + "\t");
            sw.WriteLine();
            if (LeftTop != null)
            {
                LeftTop.SaveBody(sw);
                RightTop.SaveBody(sw);
                LeftBot.SaveBody(sw);
                RightBot.SaveBody(sw);
            }
        }
        //Load quad tree ngược lên
        public void Load(StreamReader rd, List<GameObject> l_Obj, int tileNum)
        {
            
            string str = rd.ReadLine();
            str = str.Replace("\t\t\t", "\t");
            str = str.Replace("\t\t", "\t");
            string[] s = str.Split('\t');

            int id = int.Parse(s[0]);
            int x = int.Parse(s[1]);
            int y = int.Parse(s[2]);
            int w = int.Parse(s[3]);
            int h = int.Parse(s[4]);
            int n_c = int.Parse(s[5]);
            int o_c = int.Parse(s[6]);

            point = new Point(x, y);
            size = new Size(w, h);
            this.listObjects.Clear();

            for (int i = 0; i < o_c; i++)
            {
                int k = int.Parse(s[6 + i]);
                if(k >= tileNum)
                    listObjects.Add(l_Obj[k]);
            }
        }
    }   
}
